package lpg;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//
// LexStream holds a stream of tokens generated from an input and
// provides methods to retrieve information from the stream.
//
class LexStream
{
    //
    // Constructors
    //
    LexStream(Option option)
    {
        this.option = option;
        option.setLexStream(this);
    }

    int next(int i)
    {
        return (++i < tokenStream.size() ? i : tokenStream.size() - 1);
    }

    int previous(int i)
    {
        return (i <= 0 ? 0 : i - 1);
    }

    int peek()
    {
        return next(index);
    }

    void reset()
    {
        reset(1);
    }

    void reset(int i)
    {
        index = previous(i);
    }

    int getToken()
    {
        return index = next(index);
    }

    int getToken(int endToken)
    {
        return index = (index < endToken ? next(index) : tokenStream.size() - 1);
    }

    int badtoken()
    {
        return 0;
    }

    int kind(int i)
    {
        return tokenStream.get(i).kind();
    }

    int startLocation(int i)
    {
        return tokenStream.get(i).startLocation();
    }

    int endLocation(int i)
    {
        return tokenStream.get(i).endLocation();
    }

    int line(int i)
    {
        return tokenStream.get(i).line();
    }

    int endLine(int i)
    {
        return tokenStream.get(i).endLine();
    }

    int column(int i)
    {
        return tokenStream.get(i).column();
    }

    int endColumn(int i)
    {
        return tokenStream.get(i).endColumn();
    }

    boolean afterEol(int i)
    {
        return (i < 1 ? true : line(i - 1) != line(i));
    }

    String fileName(int i)
    {
        return tokenStream.get(i).fileSymbol().name();
    }

    char[] inputBuffer(int i)
    {
        InputFileSymbol fileSymbol = getFileSymbol(i);
        if (fileSymbol.buffer() == null)
            fileSymbol.readInput();
        return fileSymbol.buffer();
    }

    String tokenString(int i)
    {
        int start   = startLocation(i);
        int end     = endLocation(i);
        return new String(inputBuffer(i), start, end - start + 1);
    }

    String nameString(int i)
    {
        Symbol symbol = tokenStream.get(i).getSymbol();
        return (symbol instanceof VariableSymbol ||
                symbol instanceof MacroSymbol || symbol instanceof BlockSymbol)
                        ? symbol.name()
                        : keywordName(kind(i));
    }

    int nameStringLength(int i)
    {
        Symbol symbol = tokenStream.get(i).getSymbol();
        return (symbol instanceof VariableSymbol || symbol instanceof MacroSymbol)
                    ? symbol.name().length()
                    : symbol instanceof BlockSymbol ? endLocation(i) - startLocation(i)
                                                    : keywordName(kind(i)).length();
    }

    InputFileSymbol getFileSymbol(int i)
    {
        return tokenStream.get(i).fileSymbol();
    }

    VariableSymbol getVariableSymbol(int i)
    {
        Symbol symbol = tokenStream.get(i).getSymbol();
        return (symbol instanceof VariableSymbol) ? (VariableSymbol)symbol : null;
    }

    MacroSymbol getMacroSymbol(int i)
    {
        Symbol symbol = tokenStream.get(i).getSymbol();
        return (symbol instanceof MacroSymbol) ? (MacroSymbol)symbol : null;
    }

    BlockSymbol getBlockSymbol(int i)
    {
        Symbol symbol = tokenStream.get(i).getSymbol();
        return (symbol instanceof BlockSymbol) ? (BlockSymbol)symbol : null;
    }

    int lineSegmentLength(int i)
    {
        InputFileSymbol fileSymbol = getFileSymbol(i);
        if (fileSymbol.buffer() == null)
            fileSymbol.readInput();
        int lineEnd = fileSymbol.lineLocation(line(i) + 1) - 1;
        return Tab.strlen(fileSymbol.buffer(), startLocation(i), lineEnd);
    }

    int numTokens()
    {
        return tokenStream.size();
    }

    int importedTerminal(int i)
    {
        return importedTerminals.get(i);
    }

    int numImportedTerminals()
    {
        return importedTerminals.size();
    }

    int importedFilter(int i)
    {
        return importedFilters.get(i);
    }

    int numImportedFilters()
    {
        return importedFilters.size();
    }

    FilterMacroElement filterMacro(int i)
    {
        return filterMacros.get(i);
    }

    int numFilterMacros()
    {
        return filterMacros.size();
    }

    int getNextToken(InputFileSymbol fileSymbol)
    {
        return getNextToken(fileSymbol, 0);
    }

    int getNextToken(InputFileSymbol fileSymbol, int location)
    {
        Token token = new Token();
        token.resetInfoAndSetLocation(fileSymbol, location);
        tokenStream.add(token);

        return tokenStream.size() - 1;
    }

    Token getTokenReference(int index)
    {
        return tokenStream.get(index);
    }

    void setToken(int index, Token token)
    {
        tokenStream.set(index, token);
    }

    Token getErrorToken(InputFileSymbol fileSymbol)
    {
        return getErrorToken(fileSymbol, 0);
    }

    Token getErrorToken(InputFileSymbol fileSymbol, int location)
    {
        Token errorToken = new Token();
        errorToken.resetInfoAndSetLocation(fileSymbol, location);
        errorStream.add(errorToken);

        return errorToken;
    }

    void resetTokenStream()
    {
        resetTokenStream(0);
    }

    void resetTokenStream(int size)
    {
        tokenStream.subList(size, tokenStream.size()).clear();
    }

    void addImportedTerminal(int i)
    {
        importedTerminals.add(i);
    }

    void addImportedFilter(int i)
    {
        importedFilters.add(i);
    }

    void addFilterMacro(String name, String value)
    {
        filterMacros.add(new FilterMacroElement(name, value));
    }

    InputFileSymbol findOrInsertFile(String name)
    {
        return findOrInsertFile(defaultSearchDirectory, name == null ? "" : name);
    }

    InputFileSymbol findOrInsertFile(List<String> searchDirectory, String name)
    {
        // remove brackets from bracketed symbols: <...>
        if (name.startsWith("<") && name.endsWith(">"))
            name = name.substring(1, name.length() - 1);

        File file = null;
        for (int i = 0, n = searchDirectory.size(); i < n; i++)
        {
            String directoryName = searchDirectory.get(i);
            File file_ = new File(directoryName, name);
            if (file_.isFile())
            {
                file = file_;
                break;
            }
        }

        if (file != null)
        {
            String fullFilename = file.getPath();
            //
            // No need to normalize full_filename as if two separate
            // names are used to access the same file, eventually, we
            // will have to revisit one of the names again. Therefore,
            // at worst, it may take a bit longer to detect the loop,
            // but sooner or later, it will be detected.
            //
            return fileTable.findOrInsert(fullFilename, new InputFileSymbol(fullFilename, fileTable.size()));
        }
        return null;
    }

    private String keywordName(int i)
    {
        return Lpgsym.orderedTerminalSymbols[i];
    }

    private final Option option;
    private final LookupTable<String, InputFileSymbol> fileTable = new LookupTable<String, InputFileSymbol>();
    private int index = 0;
    private final List<Token> tokenStream = new ArrayList<Token>();
    private final List<Token> errorStream = new ArrayList<Token>();
    private final IntArrayList importedTerminals = new IntArrayList();
    private final IntArrayList importedFilters = new IntArrayList();
    private final List<FilterMacroElement> filterMacros = new ArrayList<FilterMacroElement>();

    private static final List<String> defaultSearchDirectory = Collections.singletonList(null);

}
